/** @file f_filehandler.cpp
	@brief A simple file and directory handler */

#include "file/f_filehandler.h"

using namespace G3;

//==============================================================================
// Global variables
//==============================================================================
G3::FILEHANDLER G3::MainFileHandler;

//==============================================================================
// Constructors
//==============================================================================
G3::PATH_DIR::PATH_DIR () {
	zipped = false;
}

G3::FILEHANDLER::FILEHANDLER () {
	log = &MainLog;
}

//==============================================================================
// Gets target path and opens a zip file if needed
//
//	TODO:: Make more readable
//==============================================================================
PATH_DIR G3::FILEHANDLER::GetTargetPath (const std::string &filename) {
    PATH_DIR pd;

    if (filename.empty ()) {
        if (log)
            log->Report ("ERROR: FILEHANDLER::GetTargetPath: Filename argument is an empty string..\n");
        return pd;
    }

	// Tokenizes the filename with slashes as delimiters
    TOKENLIST tl = Tokenize (filename, G3_SLASH);
    std::string ext;
    int p = 0;

	pd.zipped = false;

    for (int i=0; i<tl.size (); i++) {
		// Find the first one with a dot
        p = tl [i].find (".", 1);
        // Found it and it's not a part of a ".."
        if (p >= 1 && tl [i][p - 1] != '.') {
			// Add the first package filename into our path
            pd.path += tl [i];
			// Get its extension and check if it is zip
            ext = CopyAfterDelims (tl [i], ".");

            if (strncasecmp (ext.c_str (), ".zip", 4) == 0) {
				// If we can open it, we mark it
                if (pd.zip.Open (pd.path.c_str (), "rb"))
                    pd.zipped = true;
				// We no longer need the path before our zip file
                pd.path.clear ();
            }
			ext = "";

        } else {
			// add that directory into our path
            pd.path += tl [i];
			// we don't need no pointless slashes at the end of our string
			// so add this before the last token only
			if (i < tl.size () - 1)
				pd.path += G3_SLASH;
        }
    }

	tl.clear ();

	return pd;
}

//==============================================================================
// Reads a file
//==============================================================================
byte *G3::FILEHANDLER::Read (const string &filename, byte *data, ulong *size) {

    if (filename.empty ()) {
        if (log)
            log->Report ("ERROR: FILEHANDLER::Read: Filename argument is an empty string..\n");
        return NULL;
    }

    if (size == NULL) {
        if (log)
            log->Report ("ERROR: FILEHANDLER::Read: Size argument is a NULL pointer..\n");
        return NULL;
    }

	byte *buffer = NULL;

	PATH_DIR pd = GetTargetPath (filename);
	// So that we have a zip package we wish to read a file from?
    if (pd.zipped) {
        buffer = pd.zip.Read (pd.path.c_str (), data, size);

		pd.zip.Close ();

		return buffer;
	}

	// Just a normal file..

	if (pd.path.size () <= 1) {
	    if (log)
            log->Report ("ERROR: FILEHANDLER::Read (\"%s\"): That's an empty path..\n", filename.c_str ());

		return NULL;
	}

	struct stat s;

    // Gets the attributes of the file
    if (stat (pd.path.c_str (), &s) != 0) {
        if (log)
            log->Report ("ERROR: FILEHANDLER::Read: Failed to stat the file \"%s\"..\n", pd.path.c_str ());

		return NULL;
	}

	*size = s.st_size;

	if (*size == 0) {
	    if (log)
            log->Report ("WARNING: FILEHANDLER::Read: Size of the file \"%s\" is 0..\n", pd.path.c_str ());

		return NULL;
	}

	if (data == NULL) {
	    try {
            data = new byte [*size + 1];
    	} catch (...) {
		    if (log)
                log->Report ("ERROR! FILEHANDLER::Read: Not enough memory to store %ld bytes of file \"%s\"..\n", size, pd.path.c_str ());

			return NULL;
    	}
	}

	//printf (">> File \"%s\" read %ld\n", pd.path.c_str (), *size);

	FILE *fi = fopen (pd.path.c_str (), "rb");

	if (fi) {
		if (fread (data, 1, *size, fi) != *size) {
		    if (log)
                log->Report ("ERROR! FILEHANDLER::Read: Failed to read file \"%s\"..\n", pd.path.c_str ());
		}

		fclose (fi);
		return data;
	}
	if (log)
        log->Report ("ERROR! FILEHANDLER::Read: Failed to open file \"%s\" for reading..\n", pd.path.c_str ());

	return NULL;
}

//==============================================================================
// Buffers a whole file into a filestream
//==============================================================================
CISTREAM *G3::FILEHANDLER::ReadWholeFile (const string &filename) {
	ulong size = 0;
	CISTREAM *stream = NULL;
	byte *buffer = NULL;

	if (filename.empty ()) {
	    if (log)
            log->Report ("FILEHANDLER::ReadWholeFile: Filename argument is an empty string\n");
	    return NULL;
	}

	buffer = Read (filename, NULL, &size);

	if (buffer == NULL)
		return NULL;

	stream = new CISTREAM (size + 1);

	if (!stream) {
		if (log)
			log->Report ("FILEHANDLER::ReadWholeFile: Not enough memory for a new stream (at least %ld more bytes needed)\n", sizeof (CISTREAM));
		return NULL;
	}

	if (!stream->Buffer (buffer, size)) {
	    log->Report ("FILEHANDLER::ReadWholeFile: Buffering failed..\n");
		delete stream;
		return NULL;
	}

	stream->Seek (0, STREAM::STREAM_BEGINNING);

	return stream;
}

//==============================================================================
// Writes a file
//
//	TODO:: Fix the return pointer
//==============================================================================
bool G3::FILEHANDLER::Write (const string &filename, byte *data, ulong size) {
	PATH_DIR pd;
	bool ret = false;

	Assert (!filename.empty (), "FILEHANDLER::Write: Filename argument is a NULL pointer");

	pd = GetTargetPath (filename);
	// So that we have a zip package we wish to write into?
    if (pd.zipped) {
        if (log)
            log->Report ("ERROR! FILEHANDLER::Write: Appending to a Zip archive..\n");

		ret = pd.zip.Write (pd.path.c_str (), data, size);

		pd.zip.Close ();
		return ret;
	}

	// Just a normal file..
	if (pd.path.size () <= 1) {
	    if (log)
            log->Report ("ERROR! FILEHANDLER::Write: invalid path \"%s\"..\n", pd.path.c_str ());

		return false;
    }

	FILE *fo = fopen (pd.path.c_str (), "wb");

	if (fo) {
		if (fwrite (data, 1, size, fo) != size && log) {
			log->Report ("ERROR! FILEHANDLER::Write: Failed to write to file \"%s\"..\n", pd.path.c_str ());
		}

		fclose (fo);
		return true;
	}

    if (log)
        log->Report ("ERROR! FILEHANDLER::Write: Failed to open file \"%s\" for writing..\n", pd.path.c_str ());

	return false;
}

//==============================================================================
// Writes the whole stream into a file
//==============================================================================
bool G3::FILEHANDLER::WriteWholeStream (const string &filename, STREAM *stream) {
	Assert (!filename.empty (), "FILEHANDLER::WriteWholeStream: Filename argument is an empty string");
	Assert (stream, "FILEHANDLER::WriteWholeStream: Stream argument is a NULL pointer");

	stream->Seek (0, G3::STREAM::STREAM_BEGINNING);

	byte *temp = new byte [stream->Tell (STREAM::STREAM_END) + 1];

	long ret = stream->Read (temp, stream->Tell (STREAM::STREAM_END));

	if (!Write (filename, temp, ret)) {
	    if (log)
            log->Report ("ERROR: FILEHANDLER::WriteWholeStream:  Failed to write to file \"%s\"\n", filename.c_str ());
	    delete [] temp;
		return false;
	}

	delete [] temp;
	return true;
}

//==============================================================================
// Makes either a normal directory or creates one inside a zip file
//==============================================================================
bool G3::FILEHANDLER::MkDir (const string &dir) {
	PATH_DIR pd;
	//bool ret = 0;

	Assert (!dir.empty (), "FILEHANDLER::MkDir: Directory argument is an empty string");

	pd = GetTargetPath (dir);

	// So that we have a zip package we wish to create a directory into?
    if (pd.zipped) {
        if (log)
            log->Report ("Making directories into a Zip file is not supported.. yet..\n");

        return false;
	}

	// Just a normal directory path..
#ifndef WIN32
	mkdir (pd.path.c_str (), S_IRWXU);
#else
    mkdir (pd.path.c_str ());
#endif

	return true;
}

//==============================================================================
// Erases a file (does not work for zipped directories)
//==============================================================================
bool G3::FILEHANDLER::EraseFile (const string &file) {
	PATH_DIR pd;
	//bool ret = 0;

	Assert (!file.empty (), "FILEHANDLER::EraseFile: File argument is an empty string");

	pd = GetTargetPath (file);

	// So that we have a zip package we?
    if (pd.zipped) {
        if (log)
            log->Report ("Erasing files in a Zip file is not supported.. yet..\n");

        return false;
	}

    // Just a normal directory path..
    remove (pd.path.c_str ());

    return true;
}

//==============================================================================
// Does the file exist?
//==============================================================================
bool G3::FILEHANDLER::FileExists (const string &filename) {
    if (filename.empty ())
        return false;

	PATH_DIR pd = GetTargetPath (filename);

	if (!pd.zipped) {
		FILE *f = fopen (pd.path.c_str (), "rb");
		if (!f)
			return false;
		fclose (f);
		return true;
	}

	return pd.zip.ContainsFile (pd.path.c_str ());
}

//==============================================================================
// How big the file is?
//==============================================================================
long G3::FILEHANDLER::GetFileSize (const string &filename) {
	struct stat st;
	PATH_DIR pd = GetTargetPath (filename);

	if (!pd.zipped) {
		if (!stat (pd.path.c_str (), &st))
			return 0;
		return ((long) st.st_size);
	}

	return pd.zip.GetFileSize (pd.path.c_str ());
}

//==============================================================================
// Does the file exist?
//==============================================================================
bool G3::FileExists (const string &filename) {
	Assert (!filename.empty (), "FILEHANDLER::FileExists: Filename argument is a NULL pointer");

    FILE *f = fopen (filename.c_str (), "rb");

    if (!f)
        return false;

    fclose (f);
    return true;
}
